package com.jm.common.conf.mybatis;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jm.common.bean.base.PageParam;
import com.jm.common.bean.base.PageVO;
import com.jm.common.tool.ClassTool;
import com.jm.common.tool.LogTool;
import com.jm.common.tool.ParseTool;
import com.jm.common.tool.RandomTool;
import com.jm.common.tool.function.ThrowTool;
import com.jm.common.tool.pool.ThreadPoolTool;
import com.jm.constant.SysRedisConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author kong
 */
@Slf4j
public class EnhanceServiceImpl<M extends EnhancePlusMapper<T>, T> extends ServiceImpl<M, T> implements EnhanceService<T> {
    /**
     * model redis of key 前缀
     */
    public final String KEY_PREFIX;
    public final ValueOperations<String, Object> redisValue;
    public final RedisTemplate<String, Object> redis;
    public final Class<T> classz;
    /**
     * model of 主键
     */
    public final Field primaryKeyField;
    /**
     * model of 主键类型
     */
    public final String primaryKeyType;
    /**
     * model of 主键 名
     */
    public final String primaryKeyName;
    public final RedissonClient redissonClient;

    public EnhanceServiceImpl(RedisTemplate<String, Object> redis, RedissonClient redissonClient) {
        this.redis = redis;
        this.redisValue = redis.opsForValue();
        this.redissonClient = redissonClient;
        this.classz = (Class<T>) ClassTool.getClassz(this.getClass());

        TableName tableName = this.classz.getAnnotation(TableName.class);
        this.KEY_PREFIX = EnhanceConstant.REDIS_PREFIX + tableName.value() + ":";

        this.primaryKeyField = this.getPrimaryKeyField();
        this.primaryKeyType = this.primaryKeyField.getType().getTypeName();

        //通过@TableId注解获取主键id 数据库 列名
        TableId tableId = this.primaryKeyField.getAnnotation(TableId.class);
        this.primaryKeyName = tableId.value();
    }

    public String getKeyPrefix() {
        return this.KEY_PREFIX;
    }

    public Class<T> getClassz() {
        Type type = this.getClass().getGenericSuperclass();
        Type[] types = ((ParameterizedType) type).getActualTypeArguments();
        return (Class<T>) types[1];
    }

    public String getClassName() {
        return this.classz.getSimpleName();
    }

    public Field getPrimaryKeyField() {
        //获取主键属性及类型
        List<Field> fields = FieldUtils.getFieldsListWithAnnotation(this.classz, TableId.class);
        return fields.get(0);
    }

    public List<Field> getFields() {
        return FieldUtils.getAllFieldsList(getClassz());
    }

    public String getPrimaryKeyValue(T t) throws IllegalAccessException {
        return String.valueOf(FieldUtils.readField(t, this.primaryKeyField.getName(), true));
    }

    @Override
    public void delCache(T t) {
        try {
            redis.delete(KEY_PREFIX + this.getPrimaryKeyValue(t));
        } catch (IllegalAccessException e) {
            log.error("数据删除异常 ", e);
        }
    }

    @Override
    public void delCache(Serializable id) {
        redis.delete(KEY_PREFIX + id);
    }

    @Override
    public boolean add(T t) {
        return save(t);
    }

    protected void checkPrimaryValue(final String id) {
        if (Pattern.matches("\\d*", id.replaceAll("\"", ""))) {
            long number = Long.parseLong(id);
            ThrowTool.isTrue(number <= 0).throwMsg("id不合法");
        }
    }

    @Override
    public boolean modify(T t) {
        try {
            final String id = this.getPrimaryKeyValue(t);
            this.checkPrimaryValue(id);
            //每次更新 自动填入更新时间
            Field field = ClassTool.getField(this.classz, "updateTime");
            if (field != null) {
                field.set(t, LocalDateTime.now());
            }
            final boolean flag = this.baseMapper.enhanceUpdate(t) > 0;
            this.delCache(id);
            return flag;
        } catch (Exception e) {
            log.error("数据修改异常 ", e);
        }
        return false;
    }

    @Override
    public boolean del(String sid) {
        this.checkPrimaryValue(sid);
        //将值转换为主键类型值
        Object id = ParseTool.parseObject(sid, this.primaryKeyType);
        final boolean flag = this.baseMapper.enhanceDelete(id) > 0;
        this.delCache(sid);
        return flag;
    }

    @Override
    public boolean del(List<String> ids) {
        ThrowTool.isTrue(CollectionUtils.isEmpty(ids)).throwMsg("empty collection");
        if (ids.size() > 1) {
            final boolean flag = removeBatchByIds(ids);
            CompletableFuture.runAsync(() -> redis.delete(ids.parallelStream().map(i -> KEY_PREFIX + i).collect(Collectors.toList())), ThreadPoolTool.EXECUTOR).exceptionally(LogTool::err);
            return flag;
        }
        return del(ids.get(0));
    }

    @Override
    public boolean removeBatchByIds(Collection<?> list) {
        return this.baseMapper.enhanceBatchDelete(list.parallelStream().map(String::valueOf).collect(Collectors.toList())) > 0;
    }

    @Override
    public T get(Serializable id) {
        this.checkPrimaryValue(String.valueOf(id));
        final String key = this.KEY_PREFIX + id;
        Object o = redisValue.get(key);
        T t;
        if (o == null) {
            t = getOne(new LambdaQueryWrapper<T>().apply(this.primaryKeyName + "='" + id + "'").last("limit 1"));
            if (t == null) {
                CompletableFuture.runAsync(() -> redis.delete(key), ThreadPoolTool.EXECUTOR).exceptionally(LogTool::err);
            } else {
                //1小时后失效
                CompletableFuture.runAsync(() -> redisValue.set(key, t, 3600, TimeUnit.SECONDS), ThreadPoolTool.EXECUTOR).exceptionally(LogTool::err);
            }
        } else {
            t = (T) o;
            //小于一个小时自动续期
            CompletableFuture.runAsync(() -> this.renewal(key), ThreadPoolTool.EXECUTOR).exceptionally(LogTool::err);
        }
        return t;
    }


    /**
     * 小于一个小时自动续期
     *
     * @param key redis key
     */
    public void renewal(final String key) {
        //以redis key加锁 redisson 锁的key不能有:
        RLock lock = redissonClient.getLock("lock_" + key.replaceAll(":", "_"));
        try {
            boolean is = lock.tryLock(20, 10, TimeUnit.SECONDS);
            if (is) {
                long expireTime = Optional.ofNullable(redis.getExpire(key)).orElse(0L);
                if (expireTime < 3600) {
                    //续期时间为 剩余时间+一小时+随机秒
                    CompletableFuture.runAsync(() -> redis.expire(key, expireTime + 3600 + RandomTool.random(999, 100), TimeUnit.SECONDS), ThreadPoolTool.EXECUTOR).exceptionally(LogTool::err);
                }
            }
        } catch (InterruptedException e) {
            log.error("redis 缓存续期 错误 ", e);
        } finally {
            lock.unlock();
        }

    }

    @Override
    public PageVO<T> getPage(PageParam param) throws IllegalAccessException {
        //默认过期时间 10分钟
        LambdaQueryWrapper<T> w = new LambdaQueryWrapper<>();
        return this.getPage(param, w, w, new LambdaQueryWrapper<T>()
                .select(this.classz, e -> false).last("order by id desc limit 1"), 600, "");
    }

    /**
     * @param param        分页参数
     * @param pageWrapper  查询参数 分页
     * @param lastWrapper  查询参数 最后一条数据
     * @param expireSecond 过期秒数
     * @param id           分页区分标识
     * @return 返回由id索引的数据
     * @throws IllegalAccessException 反射异常
     */
    @Override
    public PageVO<T> getPage(PageParam param, LambdaQueryWrapper<T> pageWrapper, LambdaQueryWrapper<T> countWrapper,
                             LambdaQueryWrapper<T> lastWrapper, int expireSecond, String id) throws IllegalAccessException {
        //缓存key
        final String key = SysRedisConstant.PAGE_COUNT + this.getClassName() + id;
        //获取缓存中分页总条数
        long count = Long.parseLong(Optional.ofNullable((String) redis.opsForValue().get(key)).orElse("0"));

        //下一页就是当前页最后一条数据的id   上一页就是当前页第一条数据id
        long lastId;
        //传入的当前页最后一条数据的id
        if (StringUtils.isBlank(param.getCurrentLastId())) {
            T t = getOne(lastWrapper);
            if (t == null) {
                lastId = 0;
            } else {
                lastId = Long.parseLong(this.getPrimaryKeyValue(t));
            }
        } else {
            lastId = Long.parseLong(param.getCurrentLastId());
        }

        //默认为下一页 nexted true  逆序 <    顺序 >
        if (Optional.ofNullable(param.getNexted()).orElse(Boolean.TRUE)) {
            if (param.getCurrent() == 1) {
                //第一页 <= 包含第一条数据
                pageWrapper.apply("id<=" + lastId);
            } else {
                //下一页 < 不包含上一条数据
                pageWrapper.apply("id<" + lastId);
            }
        } else {
            // > 上一页数据
            pageWrapper.apply("id <=" + Optional.ofNullable(param.getTopFirstId()).orElse("0"));
        }
        //按时间逆序 限制查询条数
        pageWrapper.last("order by id desc limit " + param.getSize());

        return new PageVO<T>().setData(list(pageWrapper)).setCurrent(param.getCurrent()).setSize(param.getSize()).setPages(count / param.getSize()).setTotal(count);
    }


    @Override
    public PageVO<T> getPage(PageParam param, List<T> pls) {
        if (CollectionUtils.isEmpty(pls)) {
            return new PageVO<>();
        }
        final int count = pls.size();
        final int current = param.getCurrent().intValue();
        final int size = param.getSize().intValue();
        //分页
        final int start = (current - 1) * size;
        final int end = Math.min(size * current, count);
        if (start > end) {
            //TODO start > end 错误待解决
            return new PageVO<T>().setPages((long) ((count + size - 1) / size)).setTotal((long) count).setSize(param.getSize()).setCurrent(param.getCurrent());
        } else {
            pls = pls.subList(start, end);
            return new PageVO<T>().setPages((long) ((count + size - 1) / size)).setTotal((long) count).setSize(param.getSize()).setCurrent(param.getCurrent()).setData(pls);
        }
    }

}
